﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Objects;
using System.Data.Metadata.Edm;
using System.Data;
using Arch.Common.Reflection;
using System.Diagnostics.Contracts;

namespace Arch.EF.Data
{
    public static class ObjectContextExtentions
    {
        public static string GetIdProperty(this ObjectContext context, Type entityType)
        {
            return context.GetEntitySet(entityType).GetIdProperty();
        }

        public static string GetIdProperty<T>(this ObjectContext context) where T : class
        {
            return context.GetEntitySet<T>().GetIdProperty();
        }

        public static EntityKey CreateEntityKey<T>(this ObjectContext context, object id) where T : class
        {
            return context.GetEntitySet<T>().CreateEntityKey(id);
        }

        public static EntityKey CreateEntityKey(this ObjectContext context, object id, Type entityType)
        {
            return context.GetEntitySet(entityType).CreateEntityKey(id);
        }

        public static EntitySetBase GetEntitySet<T>(this ObjectContext context) where T : class
        {
            return context.GetEntitySet(typeof(T));
        }

        public static EntitySetBase GetEntitySet(this ObjectContext context, Type entityType)
        {
            EntityContainer container = context.MetadataWorkspace.GetItems<EntityContainer>(DataSpace.CSpace).First();

            EntitySetBase entitySet = container.BaseEntitySets.Where(item =>
                {
                    Type t = entityType;
                    while (!t.Equals(typeof(object)))
                    {
                        if (item.ElementType.Name.Equals(t.Name)) return true;
                        t = t.BaseType;
                    }
                    return false;
                }).FirstOrDefault();

            if (entitySet == null) throw new Exception(string.Format("Cannot Find EntitySet for {0}", entityType));

            return entitySet;
        }

        public static object GetOriginal(this ObjectContext context, string entitySetName, object entity)
        {
            Type entityType = entity.GetType();
            string idPropertyName = context.GetIdProperty(entityType);
            object id = entity.get(idPropertyName);
            if (context.TryGetObjectByKey(context.CreateEntityKey(id, entityType), out entity))
            {
                context.AttachTo(entitySetName, entity);
            }
            else
            {
                throw new ArgumentException("entity does not exist");
            }
            return entity;
        }

        public static EntityState GetState(this ObjectContext context, object entity)
        {
            ObjectStateEntry entry;
            context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry);
            return entry == null ? EntityState.Detached : entry.State;
        }

        public static IEnumerable<T> GetEntitiesInCache<T>(this ObjectContext context)
        {
            return GetEntitiesInCache<T>(context, EntityState.Added | EntityState.Modified | EntityState.Unchanged);
        }

        public static IEnumerable<T> GetEntitiesInCache<T>(this ObjectContext context, EntityState state)
        {
            Contract.Requires(context != null);
            return context.ObjectStateManager.GetObjectStateEntries(state).Select(objectStateEntry => objectStateEntry.Entity).OfType<T>();
        }

        public static void AttachAsModified<T>(this ObjectSet<T> objectSet, T entity) where T : class
        {
            objectSet.Attach(entity);
            objectSet.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
        }

        public static void AttachAllAsModified<T>(this ObjectSet<T> objectSet, IEnumerable<T> entities) where T : class
        {
            foreach (var item in entities)
            {
                objectSet.Attach(item);
                objectSet.Context.ObjectStateManager.ChangeObjectState(item, EntityState.Modified);
            }
        }
    }
}
